{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 属性"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 只读属性"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "只读属性，顾名思义，指的是只可读不可写的属性，之前我们定义的属性都是可读可写的，对于只读属性，我们需要使用 `@property` 修饰符来得到："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Leaf(object):\n",
    "    def __init__(self, mass_mg):\n",
    "        self.mass_mg = mass_mg\n",
    "    \n",
    "    # 这样 mass_oz 就变成属性了\n",
    "    @property\n",
    "    def mass_oz(self):\n",
    "        return self.mass_mg * 3.53e-5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里 `mass_oz` 就是一个只读不写的属性（注意是属性不是方法），而 `mass_mg` 是可读写的属性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.00706\n"
     ]
    }
   ],
   "source": [
    "leaf = Leaf(200)\n",
    "\n",
    "print leaf.mass_oz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以修改 `mass_mg` 属性来改变 `mass_oz`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.005295\n"
     ]
    }
   ],
   "source": [
    "leaf.mass_mg = 150\n",
    "\n",
    "print leaf.mass_oz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "是属性不是方法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'float' object is not callable",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-4-aac6717ebc82>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mleaf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmass_oz\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m: 'float' object is not callable"
     ]
    }
   ],
   "source": [
    "leaf.mass_oz()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "而且是只读属性，不可写："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "can't set attribute",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-5-d232052cd2dc>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mleaf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmass_oz\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m0.001\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mAttributeError\u001b[0m: can't set attribute"
     ]
    }
   ],
   "source": [
    "leaf.mass_oz = 0.001"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "回到 `forest` 的例子，我们希望加入几个只读属性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "class Forest(object):\n",
    "    \"\"\" Forest can grow trees which eventually die.\"\"\"\n",
    "    def __init__(self, size=(150,150)):\n",
    "        self.size = size\n",
    "        self.trees = np.zeros(self.size, dtype=bool)\n",
    "        self.fires = np.zeros((self.size), dtype=bool)\n",
    "        \n",
    "    def __repr__(self):\n",
    "        my_repr = \"{}(size={})\".format(self.__class__.__name__, self.size)\n",
    "        return my_repr\n",
    "    \n",
    "    def __str__(self):\n",
    "        return self.__class__.__name__\n",
    "    \n",
    "    @property\n",
    "    def num_cells(self):\n",
    "        \"\"\"Number of cells available for growing trees\"\"\"\n",
    "        return np.prod(self.size)\n",
    "    \n",
    "    @property\n",
    "    def tree_fraction(self):\n",
    "        \"\"\"\n",
    "        Fraction of trees\n",
    "        \"\"\"\n",
    "        num_trees = self.trees.sum()\n",
    "        return float(num_trees) / self.num_cells\n",
    "    \n",
    "    @property\n",
    "    def fire_fraction(self):\n",
    "        \"\"\"\n",
    "        Fraction of fires\n",
    "        \"\"\"\n",
    "        num_fires = self.fires.sum()\n",
    "        return float(num_fires) / self.num_cells"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "查看属性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "22500"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "forest = Forest()\n",
    "\n",
    "forest.num_cells"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "生成一个较小的森林："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "100"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "small_forest = Forest((10, 10))\n",
    "small_forest.num_cells"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "初始状态下，树和火灾的比例都是 0："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.0"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "small_forest.tree_fraction"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.0"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "small_forest.fire_fraction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 可读写的属性"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于 `@property` 生成的只读属性，我们可以使用相应的 `@attr.setter` 修饰符来使得这个属性变成可写的："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Leaf(object):\n",
    "    def __init__(self, mass_mg):\n",
    "        self.mass_mg = mass_mg\n",
    "    \n",
    "    # 这样 mass_oz 就变成属性了\n",
    "    @property\n",
    "    def mass_oz(self):\n",
    "        return self.mass_mg * 3.53e-5\n",
    "    \n",
    "    # 使用 mass_oz.setter 修饰符\n",
    "    @mass_oz.setter\n",
    "    def mass_oz(self, m_oz):\n",
    "        self.mass_mg = m_oz / 3.53e-5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "测试："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.00706\n",
      "0.005295\n"
     ]
    }
   ],
   "source": [
    "leaf = Leaf(200)\n",
    "print leaf.mass_oz\n",
    "\n",
    "leaf.mass_mg = 150\n",
    "print leaf.mass_oz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "修改 `mass_oz` 属性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "283.28611898\n"
     ]
    }
   ],
   "source": [
    "leaf.mass_oz = 0.01\n",
    "print leaf.mass_mg"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一个等价的替代如下：\n",
    "\n",
    "```python\n",
    "class Leaf(object):\n",
    "    def __init__(self, mass_mg):\n",
    "        self.mass_mg = mass_mg\n",
    "    \n",
    "    def get_mass_oz(self):\n",
    "        return self.mass_mg * 3.53e-5\n",
    "    \n",
    "    def set_mass_oz(self, m_oz):\n",
    "        self.mass_mg = m_oz / 3.53e-5\n",
    "        \n",
    "    mass_oz = property(get_mass_oz, set_mass_oz)\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
